<?php
class MP4Encoder extends BaseEncoder {
	const VIDEO_TEMP_DIR = '/data/video_infrastructure/video/tmp';

	public function getFormatId() {
		return 1;
	}
	
	public function getFormat() {
		return 'mp4';
	}

	public function encode(array $data) {
		return $this->encodeMP4($data['video_files'], $data['output_file'], $data['width'], $data['height'], $data['video_bitrate'], $data['frame_rate'], $data['pixel_aspect_ratio'], $data['audio_bitrate'], $data['audio_channel'], $data['audio_sample_rate']);
	}

	protected function encodeAAC($input_file, $audio_bitrate, $audio_channel, $audio_sample_rate, $output_file) {
		$cmd = "/opt/ffmpeg/bin/ffmpeg -y -v 0 -vn -i {$input_file} -acodec libfaac -ab {$audio_bitrate} -ac {$audio_channel} -ar {$audio_sample_rate} -vol 256 {$output_file}";
		passthru($cmd, $ret);

		return ($ret === 0);
	}

	protected function encodeH264($input_file, $width, $height, $video_bitrate, $frame_rate, $pixel_aspect_ratio, $output_file) {
		$dir = dirname($output_file);
		$filename = basename($output_file);
		$stats_file = "{$dir}/{$filename}_stats.log";
		$stats_mbtree_file = "{$dir}/{$filename}_stats.log.mbtree";
		$pass_1_file = "{$dir}/{$filename}_pass1";
		$keyint = $frame_rate * 10;
		$min_keyint = $frame_rate;
	
		$video_bitrate = $video_bitrate / 1000;
		
		$cmd = <<< CMD
/opt/ffmpeg/bin/ffmpeg -y -v 0 -an -i {$input_file} -s {$width}x{$height} -f rawvideo -r {$frame_rate} - | /opt/x264/bin/x264 -o {$pass_1_file} - {$width}x{$height} --fps {$frame_rate} --keyint {$keyint} --min-keyint {$min_keyint} --bframes 5 --analyse 0x3:0x113 --b-pyramid strict --level 51 --direct auto --weightb --quiet --no-progress --bitrate {$video_bitrate} --qcomp 0.6 --qpmin 10 --qpmax 51 --qpstep 4 --sar {$pixel_aspect_ratio} --threads 6 --pass 1 --chroma-qp-offset 0 --subme 7 --trellis 2 --8x8dct 1 --mixed-refs --ref 6 --me hex --merange 16 --stats {$stats_file}
CMD;
		passthru($cmd, $ret);
	 
		$cmd = <<< CMD
/opt/ffmpeg/bin/ffmpeg -y -v 0 -an -i {$input_file} -s {$width}x{$height} -f rawvideo -r {$frame_rate} - | /opt/x264/bin/x264 -o {$output_file} - {$width}x{$height} --fps {$frame_rate} --keyint {$keyint} --min-keyint {$min_keyint} --bframes 5 --analyse 0x3:0x113 --b-pyramid strict --level 51 --direct auto --weightb --quiet --no-progress --bitrate {$video_bitrate} --qcomp 0.6 --qpmin 10 --qpmax 51 --qpstep 4 --sar {$pixel_aspect_ratio} --threads 6 --pass 2 --chroma-qp-offset 0 --subme 7 --trellis 2 --8x8dct 1 --mixed-refs --ref 6 --me hex --merange 16 --stats {$stats_file}
CMD;
		passthru($cmd, $ret);

		unlink($pass_1_file);
		unlink($stats_file);
		unlink($stats_mbtree_file);
		
		return ($ret === 0);
	}

	protected function combine($video_file, $audio_file, $frame_rate, $output_file) {
		@unlink($output_file);
		$cmd = "/opt/gpac/bin/MP4Box -quiet -tmp /tmp -fps {$frame_rate} -add '{$video_file}#video' -add '{$audio_file}#audio' {$output_file}";
		
		passthru($cmd, $ret);
		
		return TRUE;
	}

	protected function merge($video_list, $output_file) {
		$n = count($video_list);
		
		if ($n >= 1) {
			$video_list_string = implode(" -cat ", $video_list);
			$command = "/opt/gpac/bin/MP4Box -quiet -flat -inter 0 -unhint -no-sys -no-iod -add {$video_list_string} {$output_file}";
			exec($command, $command_output, $ret);

			return ($ret === 0);
		}

		return FALSE;
	}
	
	public function encodeMP4($video_files, $output_file, $width, $height, $video_bitrate, $frame_rate, $pixel_aspect_ratio, $audio_bitrate, $audio_channel, $audio_sample_rate) {
		
		$time = time();
		$rand = rand(1, 1000000);
		$input_file = self::VIDEO_TEMP_DIR . "/merged-{$time}-{$rand}.mp4";
		$ret = self::merge($video_files, $input_file);
		
		if ($ret === FALSE) {
			error_log("ERROR: merge h264 failed");
			error_log("merge(" . implode(",", $video_files) . ", $input_file)");

			return FALSE;
		}
		
		$file = basename($input_file);
		list($filename, $ext) = explode(".", $file);
		
		$time = time();
		$rand = rand(1, 1000000);
		$temp_dir = self::VIDEO_TEMP_DIR . "/{$filename}-{$time}-{$rand}";
		$ret = @mkdir($temp_dir, 0777, TRUE);
		$temp_file="{$temp_dir}/{$rand}-{$filename}";
		$h264_file = "{$temp_file}.264";
		$m4a_file = "{$temp_file}.m4a";
		
		# H.264 encoding
		if (is_numeric($pixel_aspect_ratio)) {
			$pixel_aspect_ratio = round($pixel_aspect_ratio, 4);
			$pixel_aspect_ratio = "{$pixel_aspect_ratio}:1";
		}
		$ret = $this->encodeH264($input_file, $width, $height, $video_bitrate, $frame_rate, $pixel_aspect_ratio, $h264_file);
		if ($ret === FALSE) {
			error_log("ERROR: encode h264 failed");
			error_log("encodeH264($input_file, $width, $height, $video_bitrate, $frame_rate, $pixel_aspect_ratio, $h264_file");
			return FALSE;
		}
		
		# ACC encoding
		$ret = $this->encodeAAC($input_file, $audio_bitrate, $audio_channel, $audio_sample_rate, $m4a_file);
		if ($ret === FALSE) {
			error_log("ERROR: encode aac failed");
			error_log("encodeAAC($input_file, $audio_bitrate, $audio_sample_rate, $m4a_file)");
			return FALSE;
		}
		
		# combine the audio stream and video stream
		$ret = $this->combine($h264_file, $m4a_file, $frame_rate, $output_file);
		if ($ret === FALSE) {
			error_log("ERROR: combine mp4 failed");
			error_log("combine($h264_file, $m4a_file, $frame_rate, $output_file)");
			return FALSE;
		}
		
		# final cleanup
		unlink($h264_file);
		unlink($m4a_file);
		unlink($input_file);
		rmdir($temp_dir);
				
		return TRUE;
	}
}

$encoder = new MP4Encoder();
$ret = EncoderFactory::getInstance()->register($encoder->getFormatId(), $encoder);
?>
